package cc.unitmesh.agent.orchestrator

import cc.unitmesh.agent.config.McpToolConfigManager
import cc.unitmesh.agent.config.McpToolConfigService
import cc.unitmesh.agent.logging.getLogger
import cc.unitmesh.agent.policy.PolicyDecision
import cc.unitmesh.agent.policy.PolicyEngine
import cc.unitmesh.agent.render.CodingAgentRenderer
import cc.unitmesh.agent.state.ToolCall
import cc.unitmesh.agent.state.ToolExecutionState
import cc.unitmesh.agent.tool.*
import cc.unitmesh.agent.tool.impl.WriteFileTool
import cc.unitmesh.agent.tool.registry.ToolRegistry
import cc.unitmesh.agent.tool.shell.LiveShellExecutor
import cc.unitmesh.agent.tool.shell.LiveShellSession
import cc.unitmesh.agent.tool.shell.ShellExecutionConfig
import cc.unitmesh.agent.tool.shell.ShellExecutor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
import kotlinx.datetime.Clock

/**
 * Tool orchestrator responsible for managing tool execution workflow
 * Handles permission checking, state management, and execution coordination
 *
 * @param asyncShellExecution If true, shell commands will execute asynchronously and return
 *                            a Pending result immediately. The UI can display live terminal output
 *                            and the result will be updated when the command completes.
 *                            If false (default), shell commands will block until completion.
 */
class ToolOrchestrator(
    private val registry: ToolRegistry,
    private val policyEngine: PolicyEngine,
    private val renderer: CodingAgentRenderer,
    private val mcpConfigService: McpToolConfigService? = null,
    private val asyncShellExecution: Boolean = true
) {
    private val logger = getLogger("cc.unitmesh.agent.orchestrator.ToolOrchestrator")

    /**
     * Get the PlanStateService from the registered PlanManagementTool.
     * Returns null if no plan tool is registered.
     */
    fun getPlanStateService(): cc.unitmesh.agent.plan.PlanStateService? {
        val planTool = registry.getTool("plan") as? cc.unitmesh.agent.tool.impl.PlanManagementTool
        return planTool?.getPlanStateService()
    }

    // Coroutine scope for background tasks (async shell monitoring)
    private val backgroundScope = CoroutineScope(Dispatchers.Default)
    
    /**
     * Execute a single tool call with full orchestration
     */
    suspend fun executeToolCall(
        toolName: String,
        params: Map<String, Any>,
        context: ToolExecutionContext
    ): ToolExecutionResult {
        val toolCall = ToolCall.create(toolName, params)
        val startTime = Clock.System.now().toEpochMilliseconds()
        
        try {
            val policyDecision = policyEngine.checkPermission(toolCall, context)
            when (policyDecision) {
                PolicyDecision.DENY -> {
                    val error = "Tool execution denied by policy: $toolName"
                    return ToolExecutionResult.failure(
                        context.executionId, toolName, error, startTime, Clock.System.now().toEpochMilliseconds()
                    )
                }
                PolicyDecision.ASK_USER -> {
                    renderer.renderUserConfirmationRequest(toolName, params)
                }
                PolicyDecision.ALLOW -> {
                    // Continue with execution
                }
            }
            
            yield()
            
            val toolType = toolName.toToolType()
            val isShellTool = toolType == ToolType.Shell
            var liveSession: LiveShellSession? = null
            
            logger.debug { "Checking tool: $toolName, isShellTool: $isShellTool" }
            
            if (isShellTool) {
                // 尝试使用 PTY 执行
                val tool = registry.getTool(toolName)
                logger.debug { "Got tool: ${tool?.let { it::class.simpleName }}" }
                
                if (tool is cc.unitmesh.agent.tool.impl.ShellTool) {
                    val shellExecutor = getShellExecutor(tool)
                    logger.debug { "Shell executor: ${shellExecutor::class.simpleName}" }
                    logger.debug { "Is LiveShellExecutor: ${shellExecutor is LiveShellExecutor}" }
                    
                    if (shellExecutor is LiveShellExecutor) {
                        val supportsLive = shellExecutor.supportsLiveExecution()
                        logger.debug { "Supports live execution: $supportsLive" }
                        
                        if (supportsLive) {
                            // 准备 shell 执行配置
                            val command = params["command"] as? String
                                ?: params["cmd"] as? String
                                ?: return ToolExecutionResult.failure(
                                    context.executionId, toolName, "Shell command cannot be empty", 
                                    startTime, Clock.System.now().toEpochMilliseconds()
                                )
                            
                            logger.debug { "Starting live execution for command: $command" }
                            
                            val shellConfig = ShellExecutionConfig(
                                workingDirectory = params["workingDirectory"] as? String ?: context.workingDirectory,
                                environment = (params["environment"] as? Map<*, *>)?.mapKeys { it.key.toString() }?.mapValues { it.value.toString() } ?: context.environment,
                                timeoutMs = (params["timeoutMs"] as? Number)?.toLong() ?: context.timeout,
                                shell = params["shell"] as? String
                            )
                            
                            // 启动 PTY 会话
                            liveSession = shellExecutor.startLiveExecution(command, shellConfig)
                            logger.debug { "Live session started: ${liveSession.sessionId}" }

                            // Register session to ShellSessionManager for cancel event handling
                            val managedSession = cc.unitmesh.agent.tool.shell.ShellSessionManager.registerSession(
                                sessionId = liveSession.sessionId,
                                command = liveSession.command,
                                workingDirectory = liveSession.workingDirectory,
                                processHandle = liveSession.ptyHandle
                            )
                            // Set process handlers from LiveShellSession
                            managedSession.setProcessHandlers(
                                isAlive = { liveSession.isAlive() },
                                kill = { liveSession.kill() }
                            )
                            logger.debug { "Session registered to ShellSessionManager: ${liveSession.sessionId}" }

                            // 立即通知 renderer 添加 LiveTerminal（在执行之前！）
                            logger.debug { "Adding LiveTerminal to renderer" }
                            renderer.addLiveTerminal(
                                sessionId = liveSession.sessionId,
                                command = liveSession.command,
                                workingDirectory = liveSession.workingDirectory,
                                ptyHandle = liveSession.ptyHandle
                            )
                            logger.debug { "LiveTerminal added successfully!" }
                        }
                    }
                }
            }
            
            // Execute the tool
            val result = if (liveSession != null) {
                val shellExecutor = getShellExecutor(registry.getTool(toolName) as cc.unitmesh.agent.tool.impl.ShellTool)

                if (asyncShellExecution) {
                    // Async mode: Return Pending immediately and monitor in background
                    val command = liveSession.command
                    val sessionId = liveSession.sessionId

                    // Start background monitoring for session completion
                    startSessionMonitoring(
                        session = liveSession,
                        shellExecutor = shellExecutor as LiveShellExecutor,
                        startTime = startTime,
                        timeoutMs = context.timeout
                    )

                    // Return Pending result immediately
                    logger.debug { "Returning Pending result for async shell execution: $sessionId" }
                    ToolResult.Pending(
                        sessionId = sessionId,
                        toolName = toolName,
                        command = command,
                        message = "Executing: $command",
                        metadata = mapOf(
                            "workingDirectory" to (liveSession.workingDirectory ?: ""),
                            "isAsync" to "true"
                        )
                    )
                } else {
                    // Sync mode: Wait for completion (original behavior)
                    val exitCode = try {
                        if (shellExecutor is LiveShellExecutor) {
                            shellExecutor.waitForSession(liveSession, context.timeout)
                        } else {
                            throw ToolException("Executor does not support live sessions", ToolErrorType.NOT_SUPPORTED)
                        }
                    } catch (e: ToolException) {
                        return ToolExecutionResult.failure(
                            context.executionId, toolName, "Command execution error: ${e.message}",
                            startTime, Clock.System.now().toEpochMilliseconds()
                        )
                    } catch (e: Exception) {
                        return ToolExecutionResult.failure(
                            context.executionId, toolName, "Command execution error: ${e.message}",
                            startTime, Clock.System.now().toEpochMilliseconds()
                        )
                    }

                    // Get output from session
                    val stdout = liveSession.getStdout()
                    val metadata = mapOf(
                        "exit_code" to exitCode.toString(),
                        "execution_time_ms" to (Clock.System.now().toEpochMilliseconds() - startTime).toString(),
                        "shell" to (shellExecutor.getDefaultShell() ?: "unknown"),
                        "stdout" to stdout,
                        "stderr" to ""
                    )

                    if (exitCode == 0) {
                        ToolResult.Success(stdout, metadata)
                    } else {
                        ToolResult.Error("Command failed with exit code: $exitCode", metadata = metadata)
                    }
                }
            } else {
                // Normal execution for non-shell tools
                executeToolInternal(toolName, params, context)
            }
            
            val endTime = Clock.System.now().toEpochMilliseconds()

            // Handle Pending result specially (async shell execution)
            if (result is ToolResult.Pending) {
                return ToolExecutionResult.pending(
                    executionId = context.executionId,
                    toolName = toolName,
                    sessionId = result.sessionId,
                    command = result.command,
                    startTime = startTime,
                    metadata = result.metadata
                )
            }

            // Update final state for completed results
            val finalState = if (isSuccessResult(result)) {
                ToolExecutionState.Success(toolCall.id, result, endTime - startTime)
            } else {
                val errorMsg = getErrorMessage(result)
                ToolExecutionState.Failed(toolCall.id, errorMsg, endTime - startTime)
            }

            val metadata = result.extractMetadata()
            val finalMetadata = if (liveSession != null) {
                metadata + mapOf("isLiveSession" to "true", "sessionId" to liveSession.sessionId)
            } else {
                metadata
            }

            return ToolExecutionResult(
                executionId = context.executionId,
                toolName = toolName,
                result = result,
                startTime = startTime,
                endTime = endTime,
                retryCount = context.currentRetry,
                state = finalState,
                metadata = finalMetadata
            )
            
        } catch (e: Exception) {
            val endTime = Clock.System.now().toEpochMilliseconds()
            val error = "Tool execution failed: ${e.message}"

            return ToolExecutionResult.failure(
                context.executionId, toolName, error, startTime, endTime, context.currentRetry
            )
        }
    }
    
    /**
     * 获取 ShellTool 的执行器
     */
    private fun getShellExecutor(tool: cc.unitmesh.agent.tool.impl.ShellTool): ShellExecutor {
        return tool.getExecutor()
    }

    /**
     * Start background monitoring for an async shell session.
     * When the session completes, updates the renderer with the final status.
     */
    private fun startSessionMonitoring(
        session: LiveShellSession,
        shellExecutor: LiveShellExecutor,
        startTime: Long,
        timeoutMs: Long
    ) {
        backgroundScope.launch {
            try {
                logger.debug { "Starting background monitoring for session: ${session.sessionId}" }

                // Wait for the session to complete
                val exitCode = shellExecutor.waitForSession(session, timeoutMs)
                val endTime = Clock.System.now().toEpochMilliseconds()
                val executionTimeMs = endTime - startTime

                logger.debug { "Session ${session.sessionId} completed with exit code: $exitCode" }

                // Get output from ShellSessionManager (synced by UI's ProcessOutputCollector)
                // or fall back to LiveShellSession's stdout buffer
                val managedSession = cc.unitmesh.agent.tool.shell.ShellSessionManager.getSession(session.sessionId)
                val rawOutput = managedSession?.getOutput()?.ifEmpty { null } ?: session.getStdout()

                // Strip ANSI escape sequences for clean output to AI
                val output = cc.unitmesh.agent.tool.shell.AnsiStripper.stripAndNormalize(rawOutput)

                // Check if this was a user cancellation
                val wasCancelledByUser = managedSession?.cancelledByUser == true

                // Update renderer with final status (including cancellation info)
                renderer.updateLiveTerminalStatus(
                    sessionId = session.sessionId,
                    exitCode = exitCode,
                    executionTimeMs = executionTimeMs,
                    output = output,
                    cancelledByUser = wasCancelledByUser
                )

                logger.debug { "Updated renderer with session completion: ${session.sessionId}" }
            } catch (e: Exception) {
                logger.error(e) { "Error monitoring session ${session.sessionId}: ${e.message}" }

                // Check if this was a user cancellation and get output from managedSession
                val managedSession = cc.unitmesh.agent.tool.shell.ShellSessionManager.getSession(session.sessionId)
                val wasCancelledByUser = managedSession?.cancelledByUser == true

                logger.debug { "managedSession for ${session.sessionId}: ${managedSession != null}, cancelledByUser: $wasCancelledByUser" }

                // Get output from managedSession (which was synced during waitForSession)
                // or fall back to LiveShellSession's stdout buffer
                val managedOutput = managedSession?.getOutput()
                val sessionOutput = session.getStdout()
                val rawOutput = managedOutput?.ifEmpty { null } ?: sessionOutput

                // Strip ANSI escape sequences for clean output to AI
                val capturedOutput = rawOutput?.let {
                    cc.unitmesh.agent.tool.shell.AnsiStripper.stripAndNormalize(it)
                }

                logger.debug { "Output sources - managedOutput length: ${managedOutput?.length ?: 0}, sessionOutput length: ${sessionOutput.length}, capturedOutput length: ${capturedOutput?.length ?: 0}" }

                // Build error message with captured output
                val errorOutput = buildString {
                    appendLine("Error: ${e.message}")
                    if (!capturedOutput.isNullOrEmpty()) {
                        appendLine()
                        appendLine("Output before error:")
                        appendLine(capturedOutput)
                    }
                }

                logger.debug { "Final errorOutput length: ${errorOutput.length}" }

                // Update renderer with error status
                renderer.updateLiveTerminalStatus(
                    sessionId = session.sessionId,
                    exitCode = -1,
                    executionTimeMs = Clock.System.now().toEpochMilliseconds() - startTime,
                    output = errorOutput,
                    cancelledByUser = wasCancelledByUser
                )
            }
        }
    }

    private suspend fun executeToolInternal(
        toolName: String,
        params: Map<String, Any>,
        context: ToolExecutionContext
    ): ToolResult {
        // First try to get tool from registry (built-in tools)
        val tool = registry.getTool(toolName)
        if (tool != null) {
            // Convert orchestration context to basic tool context
            val basicContext = context.toBasicContext()
            
            // Use new ExecutableTool architecture for most tools
            // Only special-case tools that need custom handling (shell with PTY, etc.)
            return when (toolName.toToolType()) {
                ToolType.Shell -> executeShellTool(tool, params, basicContext)
                ToolType.ReadFile -> executeReadFileTool(tool, params, basicContext)
                ToolType.WriteFile -> executeWriteFileTool(tool, params, basicContext)
                ToolType.EditFile -> executeEditFileTool(tool, params, basicContext)
                ToolType.Glob -> executeGlobTool(tool, params, basicContext)
                ToolType.Grep -> executeGrepTool(tool, params, basicContext)
                ToolType.WebFetch -> executeWebFetchTool(tool, params, basicContext)
                ToolType.AskAgent -> executeAskAgentTool(tool, params, basicContext)
                else -> {
                    // Handle special tools that need parameter conversion
                    when (toolName.lowercase()) {
                        "plan" -> executePlanManagementTool(tool, params, basicContext)
                        "docql" -> executeDocQLTool(tool, params, basicContext)
                        else -> {
                            // For truly generic tools, use generic execution
                            logger.debug { "Executing tool generically: $toolName" }
                            executeGenericTool(tool, params, basicContext)
                        }
                    }
                }
            }
        }

        return executeMcpTool(toolName, params, context)
    }

    /**
     * Execute MCP tool by finding the appropriate server and delegating to McpToolConfigManager
     */
    private suspend fun executeMcpTool(
        toolName: String,
        params: Map<String, Any>,
        context: ToolExecutionContext
    ): ToolResult {
        if (mcpConfigService == null) {
            return ToolResult.Error("Tool not found: $toolName (MCP not configured)")
        }

        try {
            val serverName = findMcpServerForTool(toolName)
                ?: return ToolResult.Error("Tool not found: $toolName (no MCP server provides this tool)")

            val arguments = convertParamsToJson(params)

            val result = McpToolConfigManager.executeTool(
                serverName = serverName,
                toolName = toolName,
                arguments = arguments
            )

            return ToolResult.Success(result)

        } catch (e: Exception) {
            return ToolResult.Error("MCP tool execution failed: ${e.message}")
        }
    }

    private suspend fun findMcpServerForTool(toolName: String): String? {
        if (mcpConfigService == null) return null

        try {
            val mcpServers = mcpConfigService.getEnabledMcpServers()
            val enabledMcpTools = mcpConfigService.toolConfig.enabledMcpTools.toSet()

            // Discover tools from all servers to find which one has this tool
            val toolsByServer = McpToolConfigManager.discoverMcpTools(
                mcpServers, enabledMcpTools
            )

            // Find the server that has this tool
            for ((serverName, tools) in toolsByServer) {
                if (tools.any { it.name == toolName && it.enabled }) {
                    return serverName
                }
            }

            return null
        } catch (e: Exception) {
            logger.error(e) { "Error finding MCP server for tool '$toolName': ${e.message}" }
            return null
        }
    }

    /**
     * Convert parameters map to JSON string for MCP
     */
    private fun convertParamsToJson(params: Map<String, Any>): String {
        // Simple JSON conversion - in production you'd use a proper JSON library
        val jsonPairs = params.map { (key, value) ->
            val jsonValue = when (value) {
                is String -> "\"$value\""
                is Number -> value.toString()
                is Boolean -> value.toString()
                else -> "\"$value\""
            }
            "\"$key\": $jsonValue"
        }
        return "{${jsonPairs.joinToString(", ")}}"
    }

    private suspend fun executeShellTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        val shellTool = tool as cc.unitmesh.agent.tool.impl.ShellTool

        // Extract command - check multiple possible parameter names
        val command = params["command"] as? String
            ?: params["cmd"] as? String
            ?: params.values.firstOrNull { it is String && (it as String).isNotBlank() } as? String
            ?: return ToolResult.Error("Shell command cannot be empty")

        if (command.isBlank()) {
            return ToolResult.Error("Shell command cannot be empty")
        }

        val shellParams = cc.unitmesh.agent.tool.impl.ShellParams(
            command = command,
            workingDirectory = params["workingDirectory"] as? String,
            environment = (params["environment"] as? Map<*, *>)?.mapKeys { it.key.toString() }?.mapValues { it.value.toString() } ?: emptyMap(),
            timeoutMs = (params["timeoutMs"] as? Number)?.toLong() ?: 30000L,
            description = params["description"] as? String,
            shell = params["shell"] as? String
        )
        val invocation = shellTool.createInvocation(shellParams)
        return invocation.execute(context)
    }

    private suspend fun executeReadFileTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        val readFileTool = tool as cc.unitmesh.agent.tool.impl.ReadFileTool
        val readFileParams = cc.unitmesh.agent.tool.impl.ReadFileParams(
            path = params["path"] as? String ?: "",
            startLine = (params["startLine"] as? Number)?.toInt(),
            endLine = (params["endLine"] as? Number)?.toInt(),
            maxLines = (params["maxLines"] as? Number)?.toInt()
        )
        val invocation = readFileTool.createInvocation(readFileParams)
        return invocation.execute(context)
    }

    private suspend fun executeWriteFileTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        val writeFileTool = tool as WriteFileTool

        val path = params["path"] as? String
        val content = params["content"] as? String

        if (path.isNullOrBlank()) {
            return ToolResult.Error("File path cannot be empty")
        }

        if (content == null) {
            return ToolResult.Error("File content parameter is missing. Please provide the content parameter with the file content to write. Example: /write-file path=\"$path\" content=\"your content here\"")
        }

        // Allow empty content (blank files are valid)
        val actualContent = content

        val writeFileParams = cc.unitmesh.agent.tool.impl.WriteFileParams(
            path = path,
            content = actualContent,
            createDirectories = params["createDirectories"] as? Boolean ?: true,
            overwrite = params["overwrite"] as? Boolean ?: true,
            append = params["append"] as? Boolean ?: false
        )
        val invocation = writeFileTool.createInvocation(writeFileParams)
        return invocation.execute(context)
    }

    private suspend fun executeEditFileTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        val editFileTool = tool as cc.unitmesh.agent.tool.impl.EditFileTool

        val filePath = params["filePath"] as? String 
            ?: params["path"] as? String 
            ?: return ToolResult.Error("File path cannot be empty")
        
        val oldString = params["oldString"] as? String 
            ?: params["old_string"] as? String 
            ?: return ToolResult.Error("oldString parameter is required")
        
        val newString = params["newString"] as? String 
            ?: params["new_string"] as? String 
            ?: return ToolResult.Error("newString parameter is required")

        val expectedReplacements = (params["expectedReplacements"] as? Number)?.toInt()
            ?: (params["expected_replacements"] as? Number)?.toInt()
            ?: 1

        val editFileParams = cc.unitmesh.agent.tool.impl.EditFileParams(
            filePath = filePath,
            oldString = oldString,
            newString = newString,
            expectedReplacements = expectedReplacements
        )
        val invocation = editFileTool.createInvocation(editFileParams)
        return invocation.execute(context)
    }

    private suspend fun executeGlobTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        val globTool = tool as cc.unitmesh.agent.tool.impl.GlobTool
        val globParams = cc.unitmesh.agent.tool.impl.GlobParams(
            pattern = params["pattern"] as? String ?: "",
            path = params["path"] as? String,
            includeDirectories = params["includeDirectories"] as? Boolean ?: false,
            includeHidden = params["includeHidden"] as? Boolean ?: false,
            maxResults = (params["maxResults"] as? Number)?.toInt() ?: 1000,
            sortByTime = params["sortByTime"] as? Boolean ?: false,
            includeFileInfo = params["includeFileInfo"] as? Boolean ?: false,
            respectGitIgnore = params["respectGitIgnore"] as? Boolean ?: true
        )
        val invocation = globTool.createInvocation(globParams)
        return invocation.execute(context)
    }

    private suspend fun executeGrepTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        val grepTool = tool as cc.unitmesh.agent.tool.impl.GrepTool
        val grepParams = cc.unitmesh.agent.tool.impl.GrepParams(
            pattern = params["pattern"] as? String ?: "",
            path = params["path"] as? String,
            include = params["include"] as? String,
            exclude = params["exclude"] as? String,
            caseSensitive = params["caseSensitive"] as? Boolean ?: false,
            maxMatches = (params["maxMatches"] as? Number)?.toInt() ?: 100,
            contextLines = (params["contextLines"] as? Number)?.toInt() ?: 0,
            recursive = params["recursive"] as? Boolean ?: true
        )
        val invocation = grepTool.createInvocation(grepParams)
        return invocation.execute(context)
    }

    private suspend fun executeWebFetchTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        val webFetchTool = tool as cc.unitmesh.agent.tool.impl.WebFetchTool
        
        // Handle both prompt-only and prompt+url cases
        // If LLM provides both prompt and url, merge them
        val originalPrompt = params["prompt"] as? String ?: ""
        val url = params["url"] as? String
        
        val finalPrompt = if (url != null && url.isNotBlank()) {
            // If url is provided separately, ensure it's included in the prompt
            if (originalPrompt.contains(url)) {
                originalPrompt
            } else {
                // Prepend the URL to the prompt
                "$originalPrompt $url".trim()
            }
        } else {
            originalPrompt
        }
        
        val webFetchParams = cc.unitmesh.agent.tool.impl.WebFetchParams(
            prompt = finalPrompt
        )
        val invocation = webFetchTool.createInvocation(webFetchParams)
        return invocation.execute(context)
    }

    private suspend fun executeAskAgentTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        val askAgentTool = tool as cc.unitmesh.agent.tool.impl.AskAgentTool
        
        val agentName = params["agentName"] as? String
            ?: return ToolResult.Error("agentName parameter is required")
        val question = params["question"] as? String
            ?: return ToolResult.Error("question parameter is required")
        val contextMap = params["context"] as? Map<*, *>
        
        val askAgentParams = cc.unitmesh.agent.tool.impl.AskSubAgentParams(
            agentName = agentName,
            question = question,
            context = contextMap?.mapKeys { it.key.toString() }?.mapValues { it.value.toString() } ?: emptyMap()
        )
        
        val invocation = askAgentTool.createInvocation(askAgentParams)
        return invocation.execute(context)
    }

    private suspend fun executePlanManagementTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        val planTool = tool as cc.unitmesh.agent.tool.impl.PlanManagementTool

        val action = params["action"] as? String
            ?: return ToolResult.Error("action parameter is required")
        val planMarkdown = params["planMarkdown"] as? String ?: ""

        // Handle taskIndex and stepIndex - can be Number or String
        val taskIndex = when (val v = params["taskIndex"]) {
            is Number -> v.toInt()
            is String -> v.toIntOrNull() ?: 0
            else -> 0
        }
        val stepIndex = when (val v = params["stepIndex"]) {
            is Number -> v.toInt()
            is String -> v.toIntOrNull() ?: 0
            else -> 0
        }

        // Handle batch steps parameter
        val steps = parseStepsParameter(params["steps"])

        val planParams = cc.unitmesh.agent.tool.impl.PlanManagementParams(
            action = action,
            planMarkdown = planMarkdown,
            taskIndex = taskIndex,
            stepIndex = stepIndex,
            steps = steps
        )

        val invocation = planTool.createInvocation(planParams)
        return invocation.execute(context)
    }

    /**
     * Parse the steps parameter which can be:
     * - A List of Maps with taskIndex and stepIndex
     * - A JSON string representing an array
     * - null or empty
     */
    private fun parseStepsParameter(stepsParam: Any?): List<cc.unitmesh.agent.tool.impl.StepRef> {
        if (stepsParam == null) return emptyList()

        return when (stepsParam) {
            is List<*> -> {
                stepsParam.mapNotNull { item ->
                    when (item) {
                        is Map<*, *> -> {
                            val taskIdx = when (val t = item["taskIndex"]) {
                                is Number -> t.toInt()
                                is String -> t.toIntOrNull() ?: return@mapNotNull null
                                else -> return@mapNotNull null
                            }
                            val stepIdx = when (val s = item["stepIndex"]) {
                                is Number -> s.toInt()
                                is String -> s.toIntOrNull() ?: return@mapNotNull null
                                else -> return@mapNotNull null
                            }
                            cc.unitmesh.agent.tool.impl.StepRef(taskIdx, stepIdx)
                        }
                        else -> null
                    }
                }
            }
            is String -> {
                // Try to parse as JSON array
                try {
                    val parsed = kotlinx.serialization.json.Json.decodeFromString<List<cc.unitmesh.agent.tool.impl.StepRef>>(stepsParam)
                    parsed
                } catch (e: Exception) {
                    logger.debug { "Failed to parse steps parameter as JSON: ${e.message}" }
                    emptyList()
                }
            }
            else -> emptyList()
        }
    }

    private suspend fun executeDocQLTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        val docqlTool = tool as cc.unitmesh.agent.tool.impl.DocQLTool

        val query = params["query"] as? String
            ?: return ToolResult.Error("query parameter is required")
        val documentPath = params["documentPath"] as? String // Optional

        val docqlParams = cc.unitmesh.agent.tool.impl.DocQLParams(
            query = query,
            documentPath = documentPath
        )

        val invocation = docqlTool.createInvocation(docqlParams)
        return invocation.execute(context)
    }

    private fun isSuccessResult(result: ToolResult): Boolean {
        return when (result) {
            is ToolResult.Success -> true
            is ToolResult.AgentResult -> result.success
            is ToolResult.Error -> false
            is ToolResult.Pending -> false // Pending is not yet successful
        }
    }

    private fun getErrorMessage(result: ToolResult): String {
        return when (result) {
            is ToolResult.Error -> result.message
            is ToolResult.AgentResult -> if (!result.success) result.content else ""
            is ToolResult.Pending -> "" // Pending has no error yet
            is ToolResult.Success -> ""
        }
    }

    /**
     * Execute generic tool using ExecutableTool interface
     * This handles new tools like ask-agent, etc. without needing specific implementations
     */
    private suspend fun executeGenericTool(
        tool: Tool,
        params: Map<String, Any>,
        context: cc.unitmesh.agent.tool.ToolExecutionContext
    ): ToolResult {
        return try {
            // For SubAgent, we need to validate input first
            if (tool is cc.unitmesh.agent.core.SubAgent<*, *>) {
                @Suppress("UNCHECKED_CAST")
                val subAgent = tool as cc.unitmesh.agent.core.SubAgent<Any, ToolResult>

                // Validate and convert Map to proper input type
                val validatedInput = subAgent.validateInput(params)

                // Create invocation with validated input
                val invocation = subAgent.createInvocation(validatedInput)

                // Execute the tool
                return invocation.execute(context)
            }

            // For other ExecutableTools, use params directly
            @Suppress("UNCHECKED_CAST")
            val executableTool = tool as? ExecutableTool<Any, ToolResult>
                ?: return ToolResult.Error("Tool ${tool.name} does not implement ExecutableTool interface")

            val invocation = executableTool.createInvocation(params)

            // Execute the tool
            invocation.execute(context)
        } catch (e: Exception) {
            logger.error(e) { "Error executing generic tool ${tool.name}" }
            ToolResult.Error("Error executing tool ${tool.name}: ${e.message}")
        }
    }
}
